

package net.qiqbframework.spring.config;

import net.qiqbframework.config.ConfigurationException;
import net.qiqbframework.config.Configuration;
import net.qiqbframework.config.DefaultConfigurer;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;

import java.util.Arrays;
import java.util.Optional;

public class SpringConfigurer extends DefaultConfigurer {

    private final ConfigurableListableBeanFactory beanFactory;


    public <T> Optional<T> findBean(Class<T> componentType) {
        String[] candidates = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(beanFactory, componentType);
        if (candidates.length == 0) {
            return Optional.empty();
        } else if (candidates.length == 1) {
            return Optional.of(beanFactory.getBean(candidates[0], componentType));
        } else {
            Optional<T> primary = findPrimary(componentType, candidates);
            if (primary.isEmpty()) {
                throw new ConfigurationException("Expected single candidate for component [" + componentType.getSimpleName() + "]. Found candidates: " + Arrays.deepToString(candidates));
            }
            return primary;
        }
    }

    private <T> Optional<T> findPrimary(Class<T> componentType, String[] candidates) {
        String primary = null;
        for (String candidate : candidates) {
            if (beanFactory.getMergedBeanDefinition(candidate).isPrimary()) {
                if (primary != null) {
                    return Optional.empty();
                } else {
                    primary = candidate;
                }
            }
        }
        if (primary == null) {
            return Optional.empty();
        } else {
            return Optional.of(beanFactory.getBean(primary, componentType));
        }
    }

    public SpringConfigurer(ConfigurableListableBeanFactory beanFactory) {
        this.beanFactory = beanFactory;
    }

    @Override
    protected <T> Optional<T> defaultComponent(Class<T> type, Configuration config) {
        return this.findBean(type);
    }


}
